﻿#include "mygl/shader.h"

#include <iostream>
#include <fstream>
#include <memory>

#include <opencv2/opencv.hpp>
#include <glad/glad.h>   // 管理OpenGL的函数指针
#include <GLFW/glfw3.h>  // 提供窗口和OpenGL context

#ifndef PROJECT_ROOT
#define PROJECT_ROOT "F:/VisualStudioProjects/my-open-gl"
#endif

// settings
const std::string kProjectRoot = PROJECT_ROOT + std::string("/");
const std::string kWorkSpaceRoot = kProjectRoot + "01-GettingStarted/01-HelloTriangle/";
const std::string kShaderRoot = kWorkSpaceRoot + "";
const unsigned int kWindowWidth = 800;
const unsigned int kWindowHeight = 600;
const char* kWindowName = "HelloTriangle";

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
void WriteBmp(const std::string& filename, uint8_t* data, int width, int height);
void WriteRgbData(const std::string& filename, uint8_t* data, int width, int height);

int main() {
  // glfw: initialize and configure
  // ------------------------------
  glfwInit();
  // OpenGL 3.3
  glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
  glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
  glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);  // 核心模式

  // glfw window creation
  // --------------------
  GLFWwindow* window =
      glfwCreateWindow(kWindowWidth, kWindowHeight, kWindowName, NULL, NULL);
  if (window == NULL) {
    std::cout << "Failed to create GLFW window" << std::endl;
    glfwTerminate();
    return -1;
  }
  glfwMakeContextCurrent(window);
  glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

  // glad: load all OpenGL function pointers
  // 调用任何gl函数前，必须初始化glad
  if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
    std::cout << "Failed to initialize GLAD" << std::endl;
    return -1;
  }

  // Triangle
  // Load Shader, Compile, Link
  std::string vertex_path = kShaderRoot + "triangle_shader.vert";
  std::string fragment_path = kShaderRoot + "triangle_shader.frag";
  auto shader = std::make_unique<mygl::Shader>(vertex_path, fragment_path);
  if (shader->Init()) {
    fprintf(stderr, "Shader Init Failed!\n");
    exit(-1);
  }
  // clang-format off
  float vertices[] = {
      -0.5f, -0.5f, 0.0f,
      0.5f,  -0.5f, 0.0f, 
      0.0f,   0.5f, 0.0f
  };
  // clang-format on
  GLuint vao, vbo;
  glGenVertexArrays(1, &vao);
  glBindVertexArray(vao);
  glGenBuffers(1, &vbo);
  glBindBuffer(GL_ARRAY_BUFFER, vbo);
  glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

  glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), nullptr);
  glEnableVertexAttribArray(0);

  glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
#ifdef SAVE_VIDEO
  // 写视频需要H264编码，依赖OpenH264 Library
  // https://github.com/cisco/openh264/releases
  cv::VideoWriter video_writer;
  // int codec = cv::VideoWriter::fourcc('X', '2', '6', '4');  // OpenH264
  int codec = cv::VideoWriter::fourcc('a', 'v', 'c', '1');
  // int codec = cv::VideoWriter::fourcc('M', 'J', 'P', 'G'); // OpenCV self-contained avi
  printf("codec = %d\n", codec);
  if (!video_writer.open("triangle.mp4", codec, 25.0,
                         cv::Size(kWindowWidth, kWindowHeight), true)) {
    fprintf(stderr, "VideoWriter open %s failed\n", "save.mp4");
    exit(-1);
  }
#endif
  // render loop
  // -----------
  while (!glfwWindowShouldClose(window)) {
    // input
    // -----
    processInput(window);

    // render
    // ------
    glClear(GL_COLOR_BUFFER_BIT);

    shader->UseProgram();
    glBindVertexArray(vao);
    glDrawArrays(GL_TRIANGLES, 0, 3);
#if 0
    // 保存图片
    std::vector<uint8_t> color_data(kWindowWidth * kWindowHeight * 3);
    glPixelStorei(GL_PACK_ALIGNMENT, 4);
    glReadPixels(0, 0, kWindowWidth, kWindowHeight, GL_RGB, GL_UNSIGNED_BYTE,
                 color_data.data());
    // WriteBmp(kProjectRoot + "triangle.bmp", color_data.data(), kWindowWidth,
    // kWindowHeight);
    WriteRgbData(kProjectRoot + "triangle.rgb", color_data.data(), kWindowWidth,
                 kWindowHeight);
#endif
#ifdef SAVE_VIDEO
    cv::Mat out_mat{kWindowHeight, kWindowWidth, CV_8UC3};
    glPixelStorei(GL_PACK_ALIGNMENT, 4);
    glReadPixels(0, 0, kWindowWidth, kWindowHeight, GL_RGB, GL_UNSIGNED_BYTE,
                 out_mat.data);
    cv::flip(out_mat, out_mat, 0);
    cv::cvtColor(out_mat, out_mat, cv::COLOR_BGR2RGB);
    // cv::imwrite("triangle.png", out_mat);
    video_writer << out_mat;
#endif
    // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
    // -------------------------------------------------------------------------------
    glfwSwapBuffers(window);  // 双缓冲，后缓冲准备好与前缓冲交换，呈现在用户面前
    glfwPollEvents();
  }
  shader->DeleteProgram();
#ifdef SAVE_VIDEO
  video_writer.release();
#endif
  // glfw: terminate, clearing all previously allocated GLFW resources.
  // ------------------------------------------------------------------
  glfwTerminate();  // 清楚资源
  return 0;
}

void processInput(GLFWwindow* window) {
  if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
    glfwSetWindowShouldClose(window, true);
  }
}

void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
  // make sure the viewport matches the new window dimensions; note that width and
  // height will be significantly larger than specified on retina displays.
  glViewport(0, 0, width, height);
}

struct BitMapFileHeader {
  unsigned short bfType;       // 文件类型标识，必须为ASCII码“BM”
  unsigned long bfSize;        // 文件的尺寸，以byte为单位
  unsigned short bfReserved1;  // 保留字，必须为0
  unsigned short bfReserved2;  // 保留字，必须为0
  unsigned long bfOffBits;     // 从BITMAPFILEHEADER结构体开始到位图数据
};

struct BitMapInfoHeader {
  unsigned long biSize;     // 这个结构体的尺寸
  long biWidth;             // 位图的宽度
  long biHeight;            // 位图的长度
  unsigned short biPlanes;  // The number of planes for the target device. This value must
                            // be set to 1.
  unsigned short biBitCount;    // 一个像素有几位
  unsigned long biCompression;  // 0：不压缩，1：RLE8，2：RLE4
  unsigned long biSizeImage;    // 4字节对齐的图像数据大小
  long biXPelsPerMeter;         // 用象素/米表示的水平分辨率
  long biYPelsPerMeter;         // 用象素/米表示的垂直分辨率
  unsigned long biClrUsed;  // 实际使用的调色板索引数，0：使用所有的调色板索引
  unsigned long biClrImportant;  // 重要的调色板索引数，0：所有的调色板索引都重要
};

void WriteBmp(const std::string& filename, uint8_t* data, int width, int height) {
  const int data_size = width * height * 3;

  // file header
  BitMapFileHeader file_header;
  file_header.bfType = 0x4D42;  // 0x42是'B'；0x4D是'M'
  file_header.bfReserved1 = 0;
  file_header.bfReserved2 = 0;
  file_header.bfSize = sizeof(BitMapFileHeader) + sizeof(BitMapInfoHeader) + data_size;
  file_header.bfOffBits = sizeof(BitMapFileHeader) + sizeof(BitMapInfoHeader);

  // info header
  BitMapInfoHeader info_header{};
  info_header.biSize = sizeof(BitMapInfoHeader);
  info_header.biWidth = width;
  info_header.biHeight = height;
  info_header.biPlanes = 1;
  info_header.biBitCount = 24;
  info_header.biSizeImage = data_size;
  info_header.biCompression = 0;  // BI_RGB

  std::ofstream ofs(filename, std::ios::binary);
  if (!ofs.is_open()) {
    std::cout << "No Such File: " << filename << std::endl;
    return;
  }
  ofs.write(reinterpret_cast<char*>(&file_header), sizeof(BitMapFileHeader));
  ofs.write(reinterpret_cast<char*>(&info_header), sizeof(BitMapInfoHeader));
  // 这才是颜色数据
  ofs.write(reinterpret_cast<char*>(data), data_size);
  ofs.close();
}

void WriteRgbData(const std::string& filename, uint8_t* data, int width, int height) {
  const int data_size = width * height * 3;
  std::ofstream ofs(filename, std::ios::binary);
  if (!ofs.is_open()) {
    std::cout << "No Such File: " << filename << std::endl;
    return;
  }
  ofs.write(reinterpret_cast<const char*>(data), data_size);
  ofs.close();
}